use std::borrow::Borrow;
use std::fmt;
use std::ops::Deref;

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum Instruction {
    I_NOP,
    I_ADD,
    I_SUB,
    I_MUL,
    I_DIV,
    I_MOD,
    I_SEL(Option<Vec<Instruction>>, Option<Vec<Instruction>>),
    I_JOIN,
    I_NIL,
    I_LDC(Box<Value>),
    I_LD(i32, i32),
    I_LDF(Vec<Instruction>),
    I_AP,
    I_RAP,
    I_TAP,
    I_TRAP,
    I_RTN,
    I_DUM,
    I_CONS,
}

pub type Func = fn(&mut Vm);

#[allow(dead_code)]
#[derive(Clone)]
pub enum Value {
    IntVal(i64),
    FloatVal(f64),
    StrVal(Box<str>),
    FnVal(Func),
    LambdaVal(Vec<Instruction>, Env),
    Nil,
    ListVal(Vec<Value>),
    BoolVal(bool),
}

impl fmt::Debug for Value {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Value::IntVal(a) => write!(f, "Value<{}>", a),
            Value::FloatVal(f1) => write!(f, "Value<{}>", f1),
            Value::StrVal(s) => write!(f, "Value<\"{}\">", s),
            Value::FnVal(_) => write!(f, "Value<func>"),
            Value::LambdaVal(_, _) => write!(f, "Value<lambda>"),
            Value::Nil => write!(f, "Value<nil>"),
            Value::ListVal(a) => write!(f, "Value<list>"),
            Value::BoolVal(a) => write!(f, "Value<bool>"),
        }
    }
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Env {
    local: Vec<Value>,
    up: Option<Box<Env>>,
}

impl Env {
    fn new(up: Option<Box<Env>>) -> Env {
        return Env {
            local: Vec::new(),
            up: up,
        };
    }

    fn lookup(&self, level: i32, pos: i32) -> Option<Value> {
        let mut l = 0;
        let mut tmp = self.clone();
        loop {
            if level == l {
                if tmp.local.len() as i32 > pos {
                    let mut t = tmp
                        .local
                        .clone()
                        .get(pos as usize)
                        .expect("lookup var error")
                        .clone();
                    return Some(t);
                }
                break;
            }
            match tmp.up {
                Some(u) => {
                    tmp = *u;
                }
                _ => {
                    panic!("error env position");
                }
            }
            l += 1;
        }
        return None;
    }
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum Dump {
    Item(Vec<Instruction>, Vec<Value>, Env, Vec<Dump>, i32),
    Empty,
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Vm {
    pub stack: Vec<Value>,
    env: Env,
    code: Vec<Instruction>,
    dump: Vec<Dump>,
    cp: i32,
}

impl Vm {
    pub fn new() -> Vm {
        return Vm {
            stack: Vec::new(),
            env: Env {
                local: Vec::new(),
                up: None,
            },
            code: Vec::new(),
            dump: Vec::new(),
            cp: 0,
        };
    }

    pub fn nil(&mut self) {
        self.stack.push(Value::Nil);
        self.cp += 1;
    }

    pub fn add(&mut self) {
        let b = self.stack.pop();
        let a = self.stack.pop();
        let mut a_i: i64 = 0;
        let mut a_f: f64 = 0.0;
        let mut a_is_f = false;
        let mut ret: Value;
        match a {
            None => panic!("add argument1 error"),
            Some(v) => match v {
                Value::IntVal(ai) => {
                    a_i = ai;
                }
                Value::FloatVal(af) => {
                    a_is_f = true;
                    a_f = af;
                }
                _ => panic!("add argument1 type error"),
            },
        }
        match b {
            None => panic!("add argument2 error"),
            Some(v) => match v {
                Value::IntVal(bi) => {
                    if a_is_f {
                        ret = Value::FloatVal(a_f + (bi as f64));
                    } else {
                        ret = Value::IntVal(a_i + bi);
                    }
                }
                Value::FloatVal(bf) => {
                    if a_is_f {
                        ret = Value::FloatVal(a_f + bf);
                    } else {
                        ret = Value::FloatVal((a_i as f64) + bf);
                    }
                }
                _ => panic!("add argument2 type error"),
            },
        }
        self.stack.push(ret);
        self.cp += 1;
    }

    pub fn sub(&mut self) {
        let b = self.stack.pop();
        let a = self.stack.pop();
        let mut a_i: i64 = 0;
        let mut a_f: f64 = 0.0;
        let mut a_is_f = false;
        let mut ret: Value;
        match a {
            None => panic!("sub argument1 error"),
            Some(v) => match v {
                Value::IntVal(ai) => {
                    a_i = ai;
                }
                Value::FloatVal(af) => {
                    a_is_f = true;
                    a_f = af;
                }
                _ => panic!("sub argument1 type error"),
            },
        }
        match b {
            None => panic!("sub argument2 error"),
            Some(v) => match v {
                Value::IntVal(bi) => {
                    if a_is_f {
                        ret = Value::FloatVal(a_f - (bi as f64));
                    } else {
                        ret = Value::IntVal(a_i - bi);
                    }
                }
                Value::FloatVal(bf) => {
                    if a_is_f {
                        ret = Value::FloatVal(a_f - bf);
                    } else {
                        ret = Value::FloatVal((a_i as f64) - bf);
                    }
                }
                _ => panic!("sub argument2 type error"),
            },
        }
        self.stack.push(ret);
        self.cp += 1;
    }

    pub fn mul(&mut self) {
        let b = self.stack.pop();
        let a = self.stack.pop();
        let mut a_i: i64 = 0;
        let mut a_f: f64 = 0.0;
        let mut a_is_f = false;
        let mut ret: Value;
        match a {
            None => panic!("add argument1 error"),
            Some(v) => match v {
                Value::IntVal(ai) => {
                    a_i = ai;
                }
                Value::FloatVal(af) => {
                    a_is_f = true;
                    a_f = af;
                }
                _ => panic!("mul argument1 type error"),
            },
        }
        match b {
            None => panic!("mul argument2 error"),
            Some(v) => match v {
                Value::IntVal(bi) => {
                    if a_is_f {
                        ret = Value::FloatVal(a_f * (bi as f64));
                    } else {
                        ret = Value::IntVal(a_i * bi);
                    }
                }
                Value::FloatVal(bf) => {
                    if a_is_f {
                        ret = Value::FloatVal(a_f * bf);
                    } else {
                        ret = Value::FloatVal((a_i as f64) * bf);
                    }
                }
                _ => panic!("mul argument2 type error"),
            },
        }
        self.stack.push(ret);
        self.cp += 1;
    }

    pub fn div(&mut self) {
        let b = self.stack.pop();
        let a = self.stack.pop();
        let mut a_i: i64 = 0;
        let mut a_f: f64 = 0.0;
        let mut a_is_f = false;
        let mut ret: Value;
        match a {
            None => panic!("div argument1 error"),
            Some(v) => match v {
                Value::IntVal(ai) => {
                    a_i = ai;
                }
                Value::FloatVal(af) => {
                    a_is_f = true;
                    a_f = af;
                }
                _ => panic!("div argument1 type error"),
            },
        }
        match b {
            None => panic!("div argument2 error"),
            Some(v) => match v {
                Value::IntVal(bi) => {
                    if bi == 0 {
                        panic!("div argument2 be zero")
                    }
                    if a_is_f {
                        ret = Value::FloatVal(a_f / (bi as f64));
                    } else {
                        ret = Value::IntVal(a_i / bi);
                    }
                }
                Value::FloatVal(bf) => {
                    if bf == 0.0 {
                        panic!("div argument2 be zero")
                    }
                    if a_is_f {
                        ret = Value::FloatVal(a_f / bf);
                    } else {
                        ret = Value::FloatVal((a_i as f64) / bf);
                    }
                }
                _ => panic!("div argument2 type error"),
            },
        }
        self.stack.push(ret);
        self.cp += 1;
    }

    pub fn module(&mut self) {
        let b = self.stack.pop();
        let a = self.stack.pop();
        let mut a_i: i64 = 0;
        let mut ret: Value;
        match a {
            None => panic!("mod argument1 error"),
            Some(v) => match v {
                Value::IntVal(ai) => {
                    a_i = ai;
                }
                _ => panic!("mod argument1 type error"),
            },
        }
        match b {
            None => panic!("mod argument2 error"),
            Some(v) => match v {
                Value::IntVal(bi) => {
                    ret = Value::IntVal(a_i % bi);
                }
                _ => panic!("mod argument2 type error"),
            },
        }
        self.stack.push(ret);
        self.cp += 1;
    }

    pub fn ldc(&mut self, val: Value) {
        self.stack.push(val);
        self.cp += 1;
    }

    pub fn ldf(&mut self, val: Vec<Instruction>) {
        self.stack.push(Value::LambdaVal(val, self.env.clone()));
        self.cp += 1;
    }

    pub fn sel(&mut self, a: Option<Vec<Instruction>>, b: Option<Vec<Instruction>>) {
        //self.stack.push(Value::LambdaVal(val,self.env.clone()));
        //self.cp += 1;
        let cond = self.stack.pop();
        match cond {
            Some(v) => match v {
                Value::BoolVal(v1) => {
                    if v1 {
                        match a {
                            Some(a1) => {
                                self.dump.push(Dump::Item(
                                    self.code.clone(),
                                    self.stack.clone(),
                                    self.env.clone(),
                                    self.dump.clone(),
                                    self.cp,
                                ));
                                self.code = a1;
                                self.cp = 0;
                            }
                            _ => {
                                self.cp += 1;
                            }
                        }
                    } else {
                        match b {
                            Some(b1) => {
                                self.dump.push(Dump::Item(
                                    self.code.clone(),
                                    self.stack.clone(),
                                    self.env.clone(),
                                    self.dump.clone(),
                                    self.cp,
                                ));
                                self.code = b1;
                                self.cp = 0;
                            }
                            _ => {
                                self.cp += 1;
                            }
                        }
                    }
                }
                _ => {
                    panic!("sel cond error");
                }
            },
            _ => {
                panic!("sel cond error");
            }
        }
    }

    pub fn join(&mut self) {
        let cond = self.dump.pop();
        match cond {
            Some(v) => match v {
                Dump::Item(a, b, c, d, e) => {
                    self.code = a;
                    self.cp = e + 1;
                }
                _ => {
                    panic!("join dump error");
                }
            },
            _ => {
                panic!("join dump error");
            }
        }
    }

    pub fn ld(&mut self, level: i32, pos: i32) {
        match self.env.lookup(level, pos) {
            Some(v) => {
                self.stack.push(v);
            }
            _ => {
                panic!("ld error");
            }
        }
        self.cp += 1;
    }

    pub fn rtn(&mut self) {
        let ret = self.stack.pop().expect("rtn error ret");
        let dump = self.dump.pop().expect("rtn error dump");
        match dump {
            Dump::Item(code, stack, env, dump, cp) => {
                self.cp = cp;
                self.code = code;
                self.stack = stack;
                self.stack.push(ret);
                self.env = env;
                self.dump = dump;
            }
            Dump::Empty => {
                panic!("rtn error dump");
            }
        }
    }

    pub fn cons(&mut self) {
        let b = self.stack.pop();
        let a = self.stack.pop();
        match a {
            Some(Value::Nil) => {
                let mut tmp = Vec::new();
                match b {
                    Some(n) => {
                        tmp.push(n);
                        self.stack.push(Value::ListVal(tmp));
                    }
                    _ => {
                        panic!("cons arg2 error");
                    }
                }
            }
            Some(Value::ListVal(l)) => {
                let mut tmp = l;
                match b {
                    Some(n) => {
                        tmp.push(n);
                        self.stack.push(Value::ListVal(tmp));
                    }
                    _ => {
                        panic!("cons arg2 error");
                    }
                }
            }
            _ => {
                panic!("cons arg1 error");
            }
        }
        self.cp += 1;
    }

    pub fn ap(&mut self) {
        let func = self.stack.pop();
        let args = self.stack.pop();
        match func {
            Some(Value::LambdaVal(f, e)) => {
                self.cp += 1;
                let d = Dump::Item(
                    self.code.clone(),
                    self.stack.clone(),
                    self.env.clone(),
                    self.dump.clone(),
                    self.cp,
                );
                self.dump = Vec::new();
                self.dump.push(d);
                self.code = f;
                self.stack = Vec::new();

                self.cp = 0;
                match args {
                    Some(Value::Nil) => {
                        self.env = Env::new(Some(Box::new(self.env.clone())));
                    }
                    Some(Value::ListVal(a)) => {
                        self.env = Env::new(Some(Box::new(self.env.clone())));
                        self.env.local = a;
                    }
                    _ => {
                        panic!("ap args error");
                    }
                }
            }
            //todo fn
            _ => {
                panic!("ap args not function");
            }
        }
    }

    pub fn run(&mut self, code: &mut Vec<Instruction>) {
        self.cp = 0;
        self.code = code.clone();
        while self.cp < self.code.len() as i32 {
            match &self.code[self.cp as usize] {
                Instruction::I_NOP => {
                    println!("NOP!");
                }
                Instruction::I_ADD => {
                    self.add();
                }
                Instruction::I_MUL => {
                    self.mul();
                }
                Instruction::I_SUB => {
                    self.sub();
                }
                Instruction::I_DIV => {
                    self.div();
                }
                Instruction::I_MOD => {
                    self.module();
                }
                Instruction::I_AP => {
                    self.ap();
                }
                Instruction::I_RTN => {
                    self.rtn();
                }
                Instruction::I_SEL(a, b) => {
                    self.sel((*a).clone(), (*b).clone());
                }
                Instruction::I_JOIN => {
                    self.join();
                }
                Instruction::I_RAP => {}
                Instruction::I_DUM => {}
                Instruction::I_TAP => {}
                Instruction::I_TRAP => {}
                Instruction::I_LDC(v) => {
                    self.ldc((**v).clone());
                }
                Instruction::I_LD(level, pos) => {
                    self.ld(*level, *pos);
                }
                Instruction::I_LDF(f) => {
                    self.ldf(f.clone());
                }
                Instruction::I_NIL => {
                    self.nil();
                }
                Instruction::I_CONS => {
                    self.cons();
                }
            }
        }
    }
}
